package at.fhhagenberg.hydrac.backend.codegenerators;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.eclipse.jdt.annotation.NonNull;

import at.fhhagenberg.hydrac.backend.javacc.generator.Comment;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTActParamList;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTAddrOfStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTAlgDecl;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTAlgDef;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTAlgHead;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTAlgParamList;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTAndExpr;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTArraySpec;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTArrayType;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTAssignStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTAttrSpec;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTBlock;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTBreakStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTCallStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTChar;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTClassType;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTCompoundSpec;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTCompoundType;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTConstDecl;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTDeleteStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTElseBlock;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTExpr;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTExprList;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTFact;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTFalseFact;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTForStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTHaltStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTIdent;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTIdentList;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTIfStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTInheritedFact;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTInputStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTInteger;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTMethodDecl;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTMethodDef;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTNestedExpr;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTNewStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTNotFact;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTNullFact;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTOrExpr;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTOutputStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTParamKind;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTPointer;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTProgram;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTQualifier;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTReal;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTRelExpr;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTRepeatStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTReturnStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTReturnType;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTSelector;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTSimpleExpr;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTSingleOutput;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTStart;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTString;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTTerm;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTTextStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTThisFact;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTTrueFact;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTType;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTTypeDecl;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTTypeSpec;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTVarDecl;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTVarSpec;
import at.fhhagenberg.hydrac.backend.javacc.nodes.ASTWhileStat;
import at.fhhagenberg.hydrac.backend.javacc.nodes.Node;

import com.google.common.base.Preconditions;

public abstract class AbstractCodeGenerator {

  protected StringBuilder sb = null;
  protected FileWriter fw = null;
  protected List<Comment> comments;
  protected Map<String, String> inheritanceRelationship;
  protected int scope = 0;
  protected int currentLine = 1;
  protected String currentScopeName = "";
  protected int nrOfSpaces = 2;
  protected String fileName;
  protected String beginComment;
  protected String endComment;
  protected Logger out;
  private ASTStart astTree;

  public AbstractCodeGenerator(String fileName, ASTStart astTree, int nrOfSpaces, String beginComment, String endComment, Logger logger)
  {
    this.astTree = astTree;
    out = logger;
    if (astTree.getComments() == null) {
      this.comments = new ArrayList<>();
    } else {
      this.comments = astTree.getComments();
    }
    this.inheritanceRelationship = new HashMap<>();
    this.beginComment = beginComment;
    this.endComment = endComment;
    this.nrOfSpaces = nrOfSpaces;
    sb = new StringBuilder();
    this.fileName = fileName;
  }

  /**
   * @return Logger which will be used by the Compiler for Output
   */
  public Logger getLogger() {
    return out;
  }

  public void process() {
    handleNode(astTree);
  }

  // write stringbuilder to file
  public void generateFile() throws IOException {
    fw = new FileWriter(fileName);
    fw.append(sb.toString());
    if (fw != null) {
      fw.close();
    }
  }

  // add string to sb
  protected void addString(String string, int lineNumber) {
    for (int i = currentLine; i < lineNumber; i++) {
      insertComment(i);
      sb.append("\n");
    }
    if (currentLine < lineNumber) {
      scopeAddSpaces();
    }
    if (lineNumber != -1 && lineNumber > currentLine) {
      currentLine = lineNumber;
    }
    sb.append(string);
  }

  // add string to sb
  protected void addString(String string) {
    sb.append(string);
  }

  // add string to sb
  protected void addNewLineAndString(String string) {
    insertComment(currentLine);
    sb.append("\n");
    currentLine++;
    scopeAddSpaces();
    sb.append(string);
  }

  // increase indentation
  protected void enterScope() {
    scope++;
  }

  // decrease indentation
  protected void leaveScope() {
    if (scope > 0) {
      scope--;
    }
  }

  // add spaces, dependent on scopeLevel
  protected void scopeAddSpaces() {
    for (int i = 0; i < scope * nrOfSpaces; i++) {
      sb.append(" ");
    }
  }

  // add comments
  protected void insertComment(int i) {
    for (final Comment c : comments) {
      if (c.getLineNumber() == i) {
        scopeAddSpaces();
        sb.append(beginComment + c.getText() + endComment);
      }
    }
  }

  // reset stringbuilder
  protected void resetStream() {
    sb = null;
  }

  /**
   * The method call the handleNode method for each child of the given parent node
   * 
   * @param parent
   *          @
   */
  protected void handleChildren(@NonNull Node parent) {
    handleChildren(parent, 0);
  }

  /**
   * The method call the handleNode method for each child of the given parent node
   * 
   * @param parent
   * @param startIndex
   */
  protected void handleChildren(@NonNull Node parent, int startIndex) {
    Preconditions.checkNotNull(parent);

    for (int i = startIndex; i < parent.jjtGetNumChildren(); i++) {
      handleNode(parent.jjtGetChild(i));
    }
  }

  // evaluate class of a node, call specific method
  protected void handleNode(Node node) {
    if (node.getClass().equals(ASTActParamList.class)) {
      handleASTActParamList((ASTActParamList) node);
    } else if (node.getClass().equals(ASTAddrOfStat.class)) {
      handleASTAddrOfStat((ASTAddrOfStat) node);
    } else if (node.getClass().equals(ASTAlgDecl.class)) {
      handleASTAlgDecl((ASTAlgDecl) node);
    } else if (node.getClass().equals(ASTAlgDef.class)) {
      handleASTAlgDef((ASTAlgDef) node);
    } else if (node.getClass().equals(ASTAlgHead.class)) {
      handleASTAlgHead((ASTAlgHead) node);
    } else if (node.getClass().equals(ASTAlgParamList.class)) {
      handleASTAlgParamList((ASTAlgParamList) node);
    } else if (node.getClass().equals(ASTAndExpr.class)) {
      handleASTAndExpr((ASTAndExpr) node);
    } else if (node.getClass().equals(ASTArraySpec.class)) {
      handleASTArraySpec((ASTArraySpec) node);
    } else if (node.getClass().equals(ASTArrayType.class)) {
      handleASTArrayType((ASTArrayType) node);
    } else if (node.getClass().equals(ASTAssignStat.class)) {
      handleASTAssignStat((ASTAssignStat) node);
    } else if (node.getClass().equals(ASTAttrSpec.class)) {
      handleASTAttrSpec((ASTAttrSpec) node);
    } else if (node.getClass().equals(ASTBlock.class)) {
      handleASTBlock((ASTBlock) node);
    } else if (node.getClass().equals(ASTBreakStat.class)) {
      handleASTBreakStat((ASTBreakStat) node);
    } else if (node.getClass().equals(ASTCallStat.class)) {
      handleASTCallStat((ASTCallStat) node);
    } else if (node.getClass().equals(ASTChar.class)) {
      handleASTChar((ASTChar) node);
    } else if (node.getClass().equals(ASTClassType.class)) {
      handleASTClassType((ASTClassType) node);
    } else if (node.getClass().equals(ASTCompoundSpec.class)) {
      handleASTCompoundSpec((ASTCompoundSpec) node);
    } else if (node.getClass().equals(ASTCompoundType.class)) {
      handleASTCompoundType((ASTCompoundType) node);
    } else if (node.getClass().equals(ASTConstDecl.class)) {
      handleASTConstDecl((ASTConstDecl) node);
    } else if (node.getClass().equals(ASTDeleteStat.class)) {
      handleASTDeleteStat((ASTDeleteStat) node);
    } else if (node.getClass().equals(ASTElseBlock.class)) {
      handleASTElseBlock((ASTElseBlock) node);
    } else if (node.getClass().equals(ASTExpr.class)) {
      handleASTExpr((ASTExpr) node);
    } else if (node.getClass().equals(ASTExprList.class)) {
      handleASTExprList((ASTExprList) node);
    } else if (node.getClass().equals(ASTFact.class)) {
      handleASTFact((ASTFact) node);
    } else if (node.getClass().equals(ASTFalseFact.class)) {
      handleASTFalseFact((ASTFalseFact) node);
    } else if (node.getClass().equals(ASTForStat.class)) {
      handleASTForStat((ASTForStat) node);
    } else if (node.getClass().equals(ASTHaltStat.class)) {
      handleASTHaltStat((ASTHaltStat) node);
    } else if (node.getClass().equals(ASTIdent.class)) {
      handleASTIdent((ASTIdent) node);
    } else if (node.getClass().equals(ASTIdentList.class)) {
      handleASTIdentList((ASTIdentList) node);
    } else if (node.getClass().equals(ASTIfStat.class)) {
      handleASTIfStat((ASTIfStat) node);
    } else if (node.getClass().equals(ASTInheritedFact.class)) {
      handleASTInheritedFact((ASTInheritedFact) node);
    } else if (node.getClass().equals(ASTInputStat.class)) {
      handleASTInputStat((ASTInputStat) node);
    } else if (node.getClass().equals(ASTInteger.class)) {
      handleASTInteger((ASTInteger) node);
    } else if (node.getClass().equals(ASTMethodDecl.class)) {
      handleASTMethodDecl((ASTMethodDecl) node);
    } else if (node.getClass().equals(ASTMethodDef.class)) {
      handleASTMethodDef((ASTMethodDef) node);
    } else if (node.getClass().equals(ASTNestedExpr.class)) {
      handleASTNestedExpr((ASTNestedExpr) node);
    } else if (node.getClass().equals(ASTNewStat.class)) {
      handleASTNewStat((ASTNewStat) node);
    } else if (node.getClass().equals(ASTNotFact.class)) {
      handleASTNotFact((ASTNotFact) node);
    } else if (node.getClass().equals(ASTNullFact.class)) {
      handleASTNullFact((ASTNullFact) node);
    } else if (node.getClass().equals(ASTOrExpr.class)) {
      handleASTOrExpr((ASTOrExpr) node);
    } else if (node.getClass().equals(ASTOutputStat.class)) {
      handleASTOutputStat((ASTOutputStat) node);
    } else if (node.getClass().equals(ASTParamKind.class)) {
      handleASTParamKind((ASTParamKind) node);
    } else if (node.getClass().equals(ASTPointer.class)) {
      handleASTPointer((ASTPointer) node);
    } else if (node.getClass().equals(ASTProgram.class)) {
      handleASTProgram((ASTProgram) node);
    } else if (node.getClass().equals(ASTQualifier.class)) {
      handleASTQualifier((ASTQualifier) node);
    } else if (node.getClass().equals(ASTReal.class)) {
      handleASTReal((ASTReal) node);
    } else if (node.getClass().equals(ASTRelExpr.class)) {
      handleASTRelExpr((ASTRelExpr) node);
    } else if (node.getClass().equals(ASTRepeatStat.class)) {
      handleASTRepeatStat((ASTRepeatStat) node);
    } else if (node.getClass().equals(ASTReturnStat.class)) {
      handleASTReturnStat((ASTReturnStat) node);
    } else if (node.getClass().equals(ASTReturnType.class)) {
      handleASTReturnType((ASTReturnType) node);
    } else if (node.getClass().equals(ASTSelector.class)) {
      handleASTSelector((ASTSelector) node);
    } else if (node.getClass().equals(ASTSimpleExpr.class)) {
      handleASTSimpleExpr((ASTSimpleExpr) node);
    } else if (node.getClass().equals(ASTSingleOutput.class)) {
      handleASTSingleOutput((ASTSingleOutput) node);
    } else if (node.getClass().equals(ASTStart.class)) {
      handleASTStart((ASTStart) node);
    } else if (node.getClass().equals(ASTStat.class)) {
      handleASTStat((ASTStat) node);
    } else if (node.getClass().equals(ASTString.class)) {
      handleASTString((ASTString) node);
    } else if (node.getClass().equals(ASTTerm.class)) {
      handleASTTerm((ASTTerm) node);
    } else if (node.getClass().equals(ASTTextStat.class)) {
      handleASTTextStat((ASTTextStat) node);
    } else if (node.getClass().equals(ASTThisFact.class)) {
      handleASTThisFact((ASTThisFact) node);
    } else if (node.getClass().equals(ASTTrueFact.class)) {
      handleASTTrueFact((ASTTrueFact) node);
    } else if (node.getClass().equals(ASTType.class)) {
      handleASTType((ASTType) node);
    } else if (node.getClass().equals(ASTTypeDecl.class)) {
      handleASTTypeDecl((ASTTypeDecl) node);
    } else if (node.getClass().equals(ASTTypeSpec.class)) {
      handleASTTypeSpec((ASTTypeSpec) node);
    } else if (node.getClass().equals(ASTVarDecl.class)) {
      handleASTVarDecl((ASTVarDecl) node);
    } else if (node.getClass().equals(ASTVarSpec.class)) {
      handleASTVarSpec((ASTVarSpec) node);
    } else if (node.getClass().equals(ASTWhileStat.class)) {
      handleASTWhileStat((ASTWhileStat) node);
    } else {
      System.out.println(node.getClass());
    }
  }

  protected abstract void handleASTActParamList(ASTActParamList node);

  protected abstract void handleASTAddrOfStat(ASTAddrOfStat node);

  protected abstract void handleASTAlgDecl(ASTAlgDecl node);

  protected abstract void handleASTAlgDef(ASTAlgDef node);

  protected abstract void handleASTAlgHead(ASTAlgHead node);

  protected abstract void handleASTAlgParamList(ASTAlgParamList node);

  protected abstract void handleASTAndExpr(ASTAndExpr node);

  protected abstract void handleASTArraySpec(ASTArraySpec node);

  protected abstract void handleASTArrayType(ASTArrayType node);

  protected abstract void handleASTAssignStat(ASTAssignStat node);

  protected abstract void handleASTAttrSpec(ASTAttrSpec node);

  protected abstract void handleASTBlock(ASTBlock node);

  protected abstract void handleASTBreakStat(ASTBreakStat node);

  protected abstract void handleASTCallStat(ASTCallStat node);

  protected abstract void handleASTChar(ASTChar node);

  protected abstract void handleASTClassType(ASTClassType node);

  protected abstract void handleASTCompoundSpec(ASTCompoundSpec node);

  protected abstract void handleASTCompoundType(ASTCompoundType node);

  protected abstract void handleASTConstDecl(ASTConstDecl node);

  protected abstract void handleASTDeleteStat(ASTDeleteStat node);

  protected abstract void handleASTElseBlock(ASTElseBlock node);

  protected abstract void handleASTExpr(ASTExpr node);

  protected abstract void handleASTExprList(ASTExprList node);

  protected abstract void handleASTFact(ASTFact node);

  protected abstract void handleASTFalseFact(ASTFalseFact node);

  protected abstract void handleASTForStat(ASTForStat node);

  protected abstract void handleASTHaltStat(ASTHaltStat node);

  protected abstract void handleASTIdent(ASTIdent node);

  protected abstract void handleASTIdentList(ASTIdentList node);

  protected abstract void handleASTIfStat(ASTIfStat node);

  protected abstract void handleASTInheritedFact(ASTInheritedFact node);

  protected abstract void handleASTInputStat(ASTInputStat node);

  protected abstract void handleASTInteger(ASTInteger node);

  protected abstract void handleASTMethodDecl(ASTMethodDecl node);

  protected abstract void handleASTMethodDef(ASTMethodDef node);

  protected abstract void handleASTNestedExpr(ASTNestedExpr node);

  protected abstract void handleASTNewStat(ASTNewStat node);

  protected abstract void handleASTNotFact(ASTNotFact node);

  protected abstract void handleASTNullFact(ASTNullFact node);

  protected abstract void handleASTOrExpr(ASTOrExpr node);

  protected abstract void handleASTOutputStat(ASTOutputStat node);

  protected abstract void handleASTParamKind(ASTParamKind node);

  protected abstract void handleASTPointer(ASTPointer node);

  protected abstract void handleASTProgram(ASTProgram node);

  protected abstract void handleASTQualifier(ASTQualifier node);

  protected abstract void handleASTReal(ASTReal node);

  protected abstract void handleASTRelExpr(ASTRelExpr node);

  protected abstract void handleASTRepeatStat(ASTRepeatStat node);

  protected abstract void handleASTReturnStat(ASTReturnStat node);

  protected abstract void handleASTReturnType(ASTReturnType node);

  protected abstract void handleASTSelector(ASTSelector node);

  protected abstract void handleASTSimpleExpr(ASTSimpleExpr node);

  protected abstract void handleASTSingleOutput(ASTSingleOutput node);

  protected abstract void handleASTStart(ASTStart node);

  protected abstract void handleASTStat(ASTStat node);

  protected abstract void handleASTString(ASTString node);

  protected abstract void handleASTTerm(ASTTerm node);

  protected abstract void handleASTTextStat(ASTTextStat node);

  protected abstract void handleASTThisFact(ASTThisFact node);

  protected abstract void handleASTTrueFact(ASTTrueFact node);

  protected abstract void handleASTType(ASTType node);

  protected abstract void handleASTTypeDecl(ASTTypeDecl node);

  protected abstract void handleASTTypeSpec(ASTTypeSpec node);

  protected abstract void handleASTVarDecl(ASTVarDecl node);

  protected abstract void handleASTVarSpec(ASTVarSpec node);

  protected abstract void handleASTWhileStat(ASTWhileStat node);
}